#include "thread.h"
#include "list.h"
#include "clock.h"
#include "stackProtector.h"
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>
#include <valgrind/valgrind.h>
#include <sys/mman.h>
#include <unistd.h>

#define true 1
#define false 0

typedef int bool;

/*
 * Pile alternative utilisée pour protéger contre les débordements de pile  
 */
stack_t* alternate_stack;

/*
 * Structure de données pour le stockage des threads
 */
struct thread
{
  ucontext_t context;

  // Thread attendant ce thread
  struct thread * waited_by;

  //Valeur de retour retourné par le thread attendu
  void * retval;

  //Priorité
  int priority;

  //Pour aider Valgrind
  int valgrind_stackid;

  //Pour signaler si un thread est en cours d'abandon 
  bool canceling;

  //Pour signaler si un thread est abandonnné
  bool canceled;
  
  int ismain;
};

static struct thread * tmain;

/*
 * Liste chainée de threads
 */
//static list * thread_queue;

/*
 * Liste des threads endormis
 */
//static list * sleep_queue;

/*
 * Liste des threads terminés
 */
static list * dead_queue;

/*
 * Thread courant
 */
//static struct thread * thread_current;



/*
 * Libère la mémoire du thread passé en paramètre
 */
void thread_free(struct thread* thread)
{
  if(!thread->ismain)
    {
      VALGRIND_STACK_DEREGISTER(thread->valgrind_stackid);
      mprotect(thread->context.uc_stack.ss_sp, getpagesize(), PROT_READ|PROT_WRITE);
      free(thread->context.uc_stack.ss_sp);
      free(thread);
    }
}

/*
 * Libère la mémoire des threads qui sont dans la queue
 */
void queue_free(list ** queue)
{
  struct thread * next_thread;
  node * tmp = (*queue)->first;

  while (tmp != NULL)
  {
    next_thread = (struct thread*) (tmp->data);
    if (next_thread != NULL)
    {
      list_remove(*queue,tmp->data);
      thread_free(next_thread);
    }
    tmp = (*queue)->first;
  }
  list_free(*queue);
}

/*
 * Lance l'exécution du premier thread de la thread_queue
 * Le current_thread doit avoir été ajouté à la thread_queue ou à la sleep_queue
 */
void thread_next()
{
#ifdef DEBUG
  printf("thread_next\n");
#endif
  // S'il n'y a plus de thread à exécuter, on retourne
  if (list_isempty(thread_queue))
    {
      thread_current = tmain;
      setcontext(&(tmain->context));
      return;
    }
  //On récupère le premier thread de la liste
  struct thread * next_thread = (struct thread*) (thread_queue->first->data);
  thread_queue = list_remove(thread_queue, next_thread);

  //On change le thread courant et on change de contexte
  struct thread * previous_thread =  thread_current;
  thread_current = next_thread;
  
  //Lancement du timer
  clockTicksStart(thread_current->priority);
  swapcontext(&(previous_thread->context), &(next_thread->context));
}

void handler_preemption(int i)
{
  if(i==SIGVTALRM){
    thread_yield();
  }
}

void handler_stackprotector(int i)
{
  if(!thread_current->ismain)
    {
      thread_exit(NULL);
    }
  exit(1);
  
}

/*
 * Fonction d'initialisation du thread du main
 */
__attribute__((constructor))
void __init__thread()
{
  thread_queue = list_new();
  sleep_queue = list_new();
  dead_queue = list_new();

  thread_current = calloc(sizeof(struct thread),1);
  tmain = thread_current;

  //Mise en place de la pile alternative
  alternate_stack = stackProtector_init(handler_stackprotector);

  thread_current->waited_by = NULL;
  thread_current->retval = NULL;
  thread_current->priority = PRIORITY_DEFAULT;
  thread_current->ismain = 1;

  //Mise en place du gestionnaire
  clockTicksInit(handler_preemption);
}

/*
 * Libère tous les threads restant à la fin du programme
 */
__attribute__((destructor))
void __free__thread()
{
#ifdef DEBUG
  printf("__free__thread\n");
#endif
  clockTicksStop();
  queue_free(&thread_queue);
  queue_free(&sleep_queue);
  queue_free(&dead_queue);

  if (thread_current != NULL)
    free(thread_current);

  //On libére la pile alternative
  stackProtector_free(alternate_stack);

  //  free(tmain);
}

/*
 * Fonction appelée pour l'exécution d'un thread
 * Permet de retourner sans thread_exit
 */
void thread_exec(void *(*func)(void *), void *funcarg)
{
#ifdef DEBUG
  printf("thread_exec\n");
#endif
  void * ret = func(funcarg);
  thread_exit(ret);
}


thread_t thread_self(void)
{
#ifdef DEBUG
  printf("thread_self\n");
#endif
  return (thread_t) thread_current;
}

int thread_create(thread_t *newthread, void *(*func)(void *), void *funcarg)
{
#ifdef DEBUG
  printf("thread_create\n");
#endif
  // On alloue la mémoire nécessaire pour le thread
  struct thread * thread = malloc(sizeof(struct thread));

  // On créé un nouveau contexte pour le thread
  getcontext(&(thread->context)); // initialisation de uc
  thread->context.uc_stack.ss_size = SIGSTKSZ;//64*1024;
  thread->context.uc_stack.ss_sp = valloc(thread->context.uc_stack.ss_size);
  mprotect(thread->context.uc_stack.ss_sp, getpagesize(), PROT_NONE);
  thread->valgrind_stackid = VALGRIND_STACK_REGISTER(thread->context.uc_stack.ss_sp, thread->context.uc_stack.ss_sp + thread->context.uc_stack.ss_size);
  thread->context.uc_link = NULL;
  makecontext(&(thread->context), (void (*)(void)) thread_exec, 2, func, funcarg);


  // On initialise la valeur de retour
  thread->retval = NULL;

  // On initialise la liste d'attente
  thread->waited_by = NULL;

  thread->ismain = 0;

  // On met la priorité par défaut
  thread->priority = PRIORITY_DEFAULT;

  // On met le thread à la fin de la liste
  thread_queue = list_add(thread_queue, thread);

  //On met l'etat d'annulation a 0
  thread->canceling= false;

  //On met l'etat annule a 0
  thread->canceled = false;
  
  *newthread = thread;
  //Lancement du timer
  clockTicksStart(thread_current->priority);
  return 0;
}

int thread_priority(thread_t thread, int priority)
{
  if(priority >= PRIORITY_MIN && priority <= PRIORITY_MAX)
    {
      ((struct thread *) thread)->priority = priority;
      return 0;
    }
  else
    return -1;
}

int thread_yield(void)
{
#ifdef DEBUG
  printf("thread_yield\n");
#endif
  //Arret du timer
  clockTicksStop();
  if(list_isempty(thread_queue)){
    return 0;
  }

  //On place le thread courant à la fin de la liste
  thread_queue = list_add(thread_queue, thread_current);

  //On passe le thread suivant en thread actif
  thread_next();
  return 0;
}

int thread_join(thread_t thread, void **retval)
{
#ifdef DEBUG
  printf("thread_join\n");
#endif
  //Arret du timer
  clockTicksStop();

  //On teste si le test est annulé ou pas
  if(((struct thread *) thread)->canceled == true){
    return(THREAD_CANCELED);
    
  }
  //Si le thread qu'on veut attendre n'est pas terminé
  if (list_find(dead_queue,thread) == NULL)
  {
    //On ajoute le thread courant à la liste des threads endormis 
    sleep_queue = list_add(sleep_queue, thread_current);

    //On indique au thread passé en paramètre qu'il est attendu par le thread courant
    if (((struct thread *)thread)->waited_by == NULL)
    {
      ((struct thread *)thread)->waited_by = (struct thread *)thread_current;
    }
    else
    {
      fprintf(stderr, "Deux appel de thread join sur le même thread\n");
      exit(1);
    }

    //On lance le thread suivant
    thread_next();
  }

  //On récupère la valeur de retour du thread attendu si nécessaire
  if (retval !=NULL)
    *retval = ((struct thread *)thread)->retval;

  //On libère le thread attendu
  dead_queue = list_remove(dead_queue,thread);
  thread_free((struct thread *)thread);

  //Lancement du timer
  clockTicksStart(thread_current->priority);
  //#####Valeur de retour à revoir####
  return 0;
}

void thread_cancel(thread_t thread)
{
#ifdef DEBUG
  printf("thread_cancel\n");
#endif

 //Arret du timer pour arreter la preemption
  clockTicksStop();

  // On teste si le thread est annulé... ca sert a rien en fait haha
  if(((struct thread *) thread)->canceled == false){

    //On test si le thread en question a recu une demande d'annulation
    if (((struct thread *) thread)->canceling == false){
      ((struct thread *) thread)->canceling = true;
    }
    
    if (list_find(thread_queue,thread) != NULL)
    {
      ((struct thread *) thread)->canceled = true;
      //On ajoute le thread courant à la liste des threads morts 
      dead_queue = list_add(dead_queue, thread);
      //On libére le thread de la liste des thread
      thread_queue = list_remove(thread_queue, thread);
    }
  }

  //Reprise du Time pour reprendre la preemption
 clockTicksStart(thread_current->priority);
  
}

void thread_exit(void *retval)
{
#ifdef DEBUG
  printf("thread_exit\n");
#endif
  //Arret du timer
  clockTicksStop();
  if (thread_current->waited_by != NULL)
  {
    //On réveille le thread attendu

    // On enlève ce thread de la sleep queue
    //sleep_queue = g_list_remove(sleep_queue,thread_current->waited_by);
    sleep_queue = list_remove(sleep_queue,thread_current->waited_by);

    // On l'ajoute à la liste des threads exécutés
    thread_queue = list_add(thread_queue,thread_current->waited_by);
  }

  // Positionnement de la valeur de retour
  thread_current->retval = retval;
  
  dead_queue = list_add(dead_queue,thread_current);


 //On lance le thread suivant
  thread_next();

  /* Cas où le thread principal (main) a déjà terminé */
  /* On ne devrait pas avoir à atteindre cette partie dans les autre cas */
  // On termine
  exit(0);
}
